package cn.kerui.manage.service.sys.impl;

import cn.kerui.common.core.constant.GlobalConstants;
import cn.kerui.common.enums.MenuTypeEnum;
import cn.kerui.common.exception.ServiceException;
import cn.kerui.common.framework.util.lang.CollectionUtil;
import cn.kerui.common.framework.util.lang.ObjectUtil;
import cn.kerui.common.framework.util.lang.StringUtil;
import cn.kerui.common.framework.util.validation.Assert;
import cn.kerui.manage.service.sys.SysMenuService;
import cn.kerui.manage.vo.sys.MetaVo;
import cn.kerui.manage.vo.sys.RouterVo;
import cn.kerui.manage.vo.sys.SysMenuVo;
import cn.kerui.repos.entities.sys.SysMenu;
import cn.kerui.repos.mapper.sys.SysMenuMapper;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * <p> 菜单管理业务层 </p>
 * <p>创建于 2024/1/8 15:36 </p>
 *
 * @author yangkai
 * @version v1.0
 * @since 1.0.0
 */
@Service
@AllArgsConstructor
@Slf4j
public class SysMenuServiceImpl implements SysMenuService {

    private SysMenuMapper sysMenuMapper;

    @Override
    public List<SysMenu> getMenu() {
        List<SysMenu> sysMenus = sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, 0L)
                .orderByAsc(SysMenu::getOrderNum));
        log.info("查询到所有父级目录, parentMenu = {} ", JSON.toJSONString(sysMenus));
        getChildMenu(sysMenus);
        return sysMenus;
    }

    /**
     * 获取用户路由列表
     * @param userId 用户Id
     * @return 路由列表
     */
    @Override
    public List<RouterVo> getRouters(Long userId) {
        log.info("开始获取{}用户路由列表", userId);
        List<SysMenu> sysMenus = sysMenuMapper.selectList(null);

        List<SysMenu> childPerms = getChildPerms(sysMenus, 0);

        return buildRouters(childPerms);
    }

    /**
     * 查询菜单列表(平铺视图)
     * @return 菜单列表(平铺视图)
     */
    @Override
    public List<SysMenu> getMenuList() {
        List<SysMenu> sysMenus = sysMenuMapper.selectList(null);
        return sysMenus;
    }

    /**
     * 新增菜单
     * @param sysMenuVo
     */
    @Override
    public void addMenu(SysMenuVo sysMenuVo) {
        checkMenuNameUnique(sysMenuVo);
        if (GlobalConstants.YES_FRAME.equals(sysMenuVo.getIsFrame()) && !StringUtil.ishttp(sysMenuVo.getPath())) {
            throw new ServiceException("新增菜单【" + sysMenuVo.getMenuName() + "】失败，地址必须以http(s)://开头");
        }
        sysMenuMapper.insert(sysMenuVo);
    }

    /**
     * 判断菜单名称是否唯一
     * @param sysMenuVo 新增菜单
     */
    private void checkMenuNameUnique(SysMenuVo sysMenuVo) {
        boolean exists = sysMenuMapper.exists(new LambdaQueryWrapper<SysMenu>()
                .eq(SysMenu::getMenuName, sysMenuVo.getMenuId())
                .eq(SysMenu::getParentId, sysMenuVo.getParentId())
                .ne(ObjectUtil.isNotNull(sysMenuVo.getMenuId()), SysMenu::getMenuId, sysMenuVo.getMenuId())
        );

        Assert.isFalse(exists, () -> new ServiceException("新增菜单【" + sysMenuVo.getMenuName() + "】失败, 菜单名称已存在"));
    }

    /**
     * 构建前端路由所需要的菜单
     * @param sysMenus 菜单列表
     * @return 路由列表
     */
    private List<RouterVo> buildRouters(List<SysMenu> sysMenus) {
        LinkedList<RouterVo> routerVos = new LinkedList<>();
        for (SysMenu sysMenu : sysMenus) {
            RouterVo routerVo = new RouterVo();
            // 根据菜单状态设置是否隐藏
            routerVo.setHidden("0".equals(sysMenu.getState()));
            routerVo.setName(sysMenu.getMenuName());
            routerVo.setPath(sysMenu.getRouterPath());
            routerVo.setComponent(sysMenu.getComponentInfo());
            routerVo.setQuery(sysMenu.getQueryParam());
            // 设置菜单的元信息, 包括名称，图标，是否缓存，路径
            routerVo.setMeta(new MetaVo(sysMenu.getMenuName(), sysMenu.getIcon(), "1".equals(sysMenu.getIsCache()), sysMenu.getPath()));
            List<SysMenu> children = sysMenu.getChildren();
            if (CollectionUtil.isNotEmpty(children) && MenuTypeEnum.M.equalsSerializableValue(sysMenu.getMenuType())) {
                // 判断是不是最上级目录，且存在子菜单, 则设置始终展示
                routerVo.setAlwaysShow(true);
                routerVo.setRedirect("noRedirect");
                routerVo.setChildren(buildRouters(children));
            } else if (sysMenu.isMenuFrame()) {
                // 判断是不是最上级菜单，设置特别的路由设置
                routerVo.setMeta(null);
                List<RouterVo> childrenList = new ArrayList<>();
                // 配置子菜单项
                RouterVo childrenVo = new RouterVo();
                childrenVo.setPath(sysMenu.getPath());
                childrenVo.setComponent(sysMenu.getComponent());
                childrenVo.setName(StringUtil.upperFirst(sysMenu.getPath()));
                childrenVo.setMeta(new MetaVo(sysMenu.getMenuName(), sysMenu.getIcon(), StringUtil.equals("1", sysMenu.getIsCache()), sysMenu.getPath()));
                childrenVo.setQuery(sysMenu.getQueryParam());
                childrenList.add(childrenVo);
                routerVo.setChildren(childrenList);
            } else if (sysMenu.getParentId().intValue() == 0 && sysMenu.isInnerLink()) {
                // 如果是内部链接且为顶级菜单，则特别处理
                routerVo.setMeta(new MetaVo(sysMenu.getMenuName(), sysMenu.getIcon()));
                routerVo.setPath("/");
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo childrenVo = new RouterVo();
                // 替换并设置内部链接的路由路径
                String routerPath = SysMenu.innerLinkReplaceEach(sysMenu.getPath());
                childrenVo.setPath(routerPath);
                childrenVo.setComponent(GlobalConstants.INNER_LINK);
                childrenVo.setName(StringUtil.upperFirst(routerPath));
                childrenVo.setMeta(new MetaVo(sysMenu.getMenuName(), sysMenu.getIcon(), sysMenu.getPath()));
                childrenList.add(childrenVo);
                routerVo.setChildren(childrenList);
            }
            routerVos.add(routerVo);
        }
        return routerVos;
    }

    /**
     * 根据父节点的ID获取所有子节点
     *
     * @param list 分类表
     * @param parentId 父节点ID
     * @return
     */
    private List<SysMenu> getChildPerms(List<SysMenu> list, final int parentId) {
        ArrayList<SysMenu> returnList = new ArrayList<>();
        for (SysMenu sysMenu : list) {
            // 1. 根据传入的父节点ID， 遍历该父节点的所有子节点
            if (sysMenu.getParentId() == parentId) {
                recursionFn(list, sysMenu);
                returnList.add(sysMenu);
            }
        }
        return returnList;
    }

    /**
     * 递归列表
     */
    private void recursionFn(List<SysMenu> list, SysMenu t) {
        // 2. 得到子节点列表
        List<SysMenu> childList = list.stream().filter(menu -> menu.getParentId().equals(t.getMenuId())).toList();
        t.setChildren(childList);
        // 判断子节点列表中是否还存在子节点
        for (SysMenu tChild : childList) {
            // 判断是否有子节点
            if (list.stream().anyMatch(x -> x.getParentId().equals(tChild.getMenuId()))) {
                // 如果存在，递归遍历该子节点
                recursionFn(list, tChild);
            }
        }
    }

    /**
     * 获取某个菜单的子菜单
     * @param sysMenus
     */
    private void getChildMenu(List<SysMenu> sysMenus) {
        for (SysMenu sysMenu : sysMenus) {
            // 如果菜单为目录则查询子菜单
            if (MenuTypeEnum.M.equalsSerializableValue(sysMenu.getMenuType()) || MenuTypeEnum.C.equalsSerializableValue(sysMenu.getMenuType())) {
                List<SysMenu> sysMenusList = sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenu>()
                        .eq(SysMenu::getParentId, sysMenu.getMenuId())
                        .orderByAsc(SysMenu::getOrderNum));
                getChildMenu(sysMenusList);
                sysMenu.setHasChildren(true);
                sysMenu.setChildren(sysMenusList);
            }
        }
    }
}
